﻿using Platformer_Example.Controller.Character;
using Platformer_Example.Controller.Collision;
using Platformer_Example.Controller.Interfaces;
using Platformer_Example.Controller.Map;
using System;
using Platformer_Example.Controller.Terrain.Castle;
using Transformable_Engine_v2.Engine.GameObjects.Containers;
using Transformable_Engine_v2.Engine.GameObjects.Primitives;
using Transformable_Engine_v2.Engine.Graphics;
using Transformable_Engine_v2.Engine.Transformable_Math.Geometry;

namespace Platformer_Example.Controller.Terrain
{
    class TerrainDescription
    {
        public BaseTerrain.TerrainSprite Sprite { get; set; }
        public BaseTerrain.TerrainType Type { get; set; }
    }

    public abstract partial class BaseTerrain : Container, ICollidable
    {
        protected TE_Rectangle _collisionBox;

        protected TerrainType _type;

        public virtual TE_Rectangle GetCollisionBox(TE_Vector2 size)
        {
            return new TE_Rectangle(0, 0, size);
        }

        private readonly bool _isStairEnd;

        public override void LoadContent()
        {
            if(_collisionBox == null)
                _collisionBox = GetCollisionBox(TE_Vector2.One).ToGridBound();

            base.LoadContent();
        }

        /// <summary>
        /// Creates a new instance of sloped terrain
        /// </summary>
        /// <param name="position">Top left position of the terrain, gridbound</param>
        protected BaseTerrain(TE_Vector2 position)
        {
            _type = GetTerrainType();
            PositionGridBound = position;

            AddSprite();
            _isStairEnd = false;
        }

        /// <summary>
        /// Creates a new instance of square terrain
        /// </summary>
        /// <param name="position">Top left position of the terrain, gridbound</param>
        /// <param name="size">Size of the terrain</param>
        /// <param name="type">Type of the terrain</param>
        /// <param name="isStairEnd">Is the terrain a stair end?</param>
        protected BaseTerrain(TE_Vector2 position, TE_Vector2 size, int type, bool isStairEnd)
        {
            TerrainDescription td = TerrainFromType(type);

            _type = td.Type;

            PositionGridBound = position;

            if(td.Sprite != TerrainSprite.None)
                AddSprite(td.Sprite, size);

            _collisionBox = GetCollisionBox(size).ToGridBound();

            _isStairEnd = isStairEnd;
        }

        protected virtual TerrainType GetTerrainType()
        {
            return TerrainType.None;
        }

        protected virtual void AddSprite()
        {
            
        }

        protected virtual void AddSprite(TerrainSprite sprite, TE_Vector2 size)
        {
            
        }

        public virtual bool IsVirtual { get { return false; } }

        public static BaseTerrain CreateStair(MapCellData.TerrainType id, MapCellData.StairType stair, TE_Vector2 position)
        {
            switch (id)
            {
                case MapCellData.TerrainType.Castle:
                    return new CastleStair(position, stair);
                default:
                    throw new ArgumentOutOfRangeException("id");
            }
        }

        public static BaseTerrain CreateStairEnd(TE_Vector2 position)
        {
            return new StairEnd(position);
        }

        public static BaseTerrain CreateTerrain(MapCellData.TerrainType id, int type, TE_Vector2 position, TE_Vector2 size)
        {
            if(TerrainFromType(type) != null)
            {
                switch (id)
                {
                    case MapCellData.TerrainType.Filler:
                        return null;
                    case MapCellData.TerrainType.Castle:
                        return new CastleTerrain(position, size, type, false);
                    default:
                        throw new ArgumentOutOfRangeException("id");
                }
            }

            throw new ArgumentOutOfRangeException("type");
        }

        public TE_Rectangle GetCollisionBox()
        {
            return _collisionBox;
        }

        public TE_Vector2 GetCollisionBoxPosition()
        {
            return AbsolutePosition;
        }

        public TE_Rectangle CollisionArea
        {
            get { return GetCollisionBoxPosition() + GetCollisionBox(); }
        }

        public bool IsTerrainValid(TerrainType collisionType)
        {
            switch (collisionType)
            {
                case TerrainType.Left:
                    return IsLeftWall;
                case TerrainType.Floor:
                    return IsFloor;
                case TerrainType.Right:
                    return IsRightWall;
                case TerrainType.Ceiling:
                    return IsCeiling;
                default:
                    throw new ArgumentOutOfRangeException("collisionType");
            }
        }

        public virtual bool IsTerrainCorrectlyLocated(TerrainType collisionType, BaseGravityAffected character)
        {
            //Use EPSILON of -0.01f to verify bounds, or else we get rounding errors
            switch (collisionType)
            {
                case TerrainType.Left:
                    return CollisionArea.Left - character.CollisionArea.Right >= -0.01f;
                case TerrainType.Floor:
                    if(_isStairEnd)
                        return CollisionArea.Bottom - character.CollisionArea.Bottom >= -0.01f;
                    return CollisionArea.Top - character.CollisionArea.Bottom >= -0.01f;
                case TerrainType.Right:
                    return character.CollisionArea.Left - CollisionArea.Right >= -0.01f;
                case TerrainType.Ceiling:
                    return character.CollisionArea.Top - CollisionArea.Bottom >= -0.01f;
                default:
                    throw new ArgumentOutOfRangeException("collisionType");
            }
        }

        public virtual bool IsTerrainCollidingInOtherAxis(TerrainType collisionType, BaseGravityAffected character)
        {
            switch (collisionType)
            {
                case TerrainType.Left:
                case TerrainType.Right:
                    return CollisionDetection.IsBetweenY(character.CollisionArea, CollisionArea);
                case TerrainType.Floor:
                case TerrainType.Ceiling:
                    return CollisionDetection.IsBetweenX(character.CollisionArea, CollisionArea);
                default:
                    throw new ArgumentOutOfRangeException("collisionType");
            }
        }

        public virtual float GetDistanceFromCharacter(TerrainType collisionType, BaseGravityAffected character)
        {
            switch (collisionType)
            {
                case TerrainType.Left:
                    return Math.Abs(CollisionArea.Left - character.CollisionArea.Right);
                case TerrainType.Floor:
                    if (_isStairEnd && character.CollisionArea.Bottom >= CollisionArea.Top)
                            return 0;
                    return Math.Abs(CollisionArea.Top - character.CollisionArea.Bottom);
                case TerrainType.Right:
                    return Math.Abs(CollisionArea.Right - character.CollisionArea.Left);
                case TerrainType.Ceiling:
                    return Math.Abs(CollisionArea.Bottom - character.CollisionArea.Top);
                default:
                    throw new ArgumentOutOfRangeException("collisionType");
            }
        }


        public virtual TerrainType ResolveCollision(BaseGravityAffected character, TerrainType collisionType, float movementOffset)
        {
            switch (collisionType)
            {
                case TerrainType.Left:
                    character.MoveAbsolute(new TE_Vector2(CollisionArea.Left - character.GetCollisionBox().Right, character.GetAbsolutePosition().Y));
                    break;
                case TerrainType.Floor:
                    character.MoveAbsolute(new TE_Vector2(character.GetAbsolutePosition().X, CollisionArea.Top - character.GetCollisionBox().Bottom));
                    break;
                case TerrainType.Right:
                    character.MoveAbsolute(new TE_Vector2(CollisionArea.Right - character.GetCollisionBox().Left, character.GetAbsolutePosition().Y));
                    break;
                case TerrainType.Ceiling:
                    character.MoveAbsolute(new TE_Vector2(character.GetAbsolutePosition().X, CollisionArea.Bottom - character.GetCollisionBox().Top));
                    break;
            }
            return collisionType;
        }
    }
}